﻿namespace MicroCloud.Entity
{
    #region "实现一个单元操作内的功能，管理单元操作内涉及的所有上下文对象及其事务"
    /// <summary>
    /// EF Core 作业单元
    /// <para>管理单元操作内涉及的所有上下文对象及其事务</para>
    /// </summary>
    public class UnitOfWork : Disposable, IUnitOfWork
    {
        //字段
        private readonly IServiceProvider _provider;
        private readonly ILogger _logger;

        private readonly ConcurrentDictionary<DbConnection, List<DbContextBase>> _contextDict;
        private readonly ConcurrentDictionary<DbConnection, DbTransaction> _transDict;

        private readonly Stack<string> _transactionStack = new();

        #region "构造函数"
        #region "初始化一个 <see cref="UnitOfWork"/> 类型的新实例"
        /// <summary>
        /// 初始化一个 <see cref="UnitOfWork"/> 类型的新实例
        /// </summary>
        /// <param name="provider">服务提供者</param>
        public UnitOfWork(IServiceProvider provider)
        {
            _provider = provider;
            _logger = provider.GetLogger<UnitOfWork>();

            _contextDict = new ConcurrentDictionary<DbConnection, List<DbContextBase>>();
            _transDict = new ConcurrentDictionary<DbConnection, DbTransaction>();
        }
        #endregion

        #endregion

        #region "属性"
        #region "获取 是否已提交"
        /// <summary>
        /// 获取 是否已提交
        /// </summary>
        public virtual bool HasCommitted { get; private set; }
        #endregion
        #region "获取 是否启用事务"
        /// <summary>
        /// 获取 是否启用事务
        /// </summary>
        public bool IsEnabledTransaction => _transactionStack.Count > 0;
        #endregion

        #endregion

        #region "方法"
        #region "获取指定实体所属的数据上下文实例"
        /// <summary>
        /// 获取指定实体所属的数据上下文实例
        /// </summary>
        /// <typeparam name="TEntity">实体类型</typeparam>
        /// <typeparam name="TKey">实体主键类型</typeparam>
        /// <returns><typeparamref name="TEntity"/> 所属的数据上下文实例</returns>
        public virtual IDbContext GetEntityDbContext<TEntity, TKey>() where TEntity : IEntity<TKey>
        {
            Type entityType = typeof(TEntity);
            return GetEntityDbContext(entityType);
        }
        #endregion
        #region "获取指定实体类型所属的数据上下文实例"
        /// <summary>
        /// 获取指定实体类型所属的数据上下文实例
        /// </summary>
        /// <param name="entityType">实体类型</param>
        /// <returns>实体所属的数据上下文实例</returns>
        public virtual IDbContext GetEntityDbContext(Type entityType)
        {
            if (!entityType.IsEntityType())
            {
                throw new Exception(I18N.T("类型 {0} 不是实体类型", entityType));
            }

            IEntityManager manager = _provider.GetRequiredService<IEntityManager>();
            Type dbContextType = manager.GetDbContextTypeForEntity(entityType);

            DbContextBase dbContext = (DbContextBase)GetDbContext(dbContextType);
            _logger.LogDebug("由实体类 {entityType} 获取到上下文 {dbContextType} 实例，上下文标识：{dbContextHashCode}。", entityType, dbContext.GetType(), dbContext.GetHashCode());

            return dbContext;
        }
        #endregion
        #region "获取指定类型的数据上下文实例"
        /// <summary>
        /// 获取指定类型的数据上下文实例
        /// </summary>
        /// <param name="dbContextType">数据上下文类型</param>
        /// <returns>数据上下文实例</returns>
        public IDbContext GetDbContext(Type dbContextType)
        {
            //已存在上下文对象，直接返回
            DbContextBase dbContext = _contextDict.SelectMany(m => m.Value).FirstOrDefault(m => m.GetType() == dbContextType);
            if (dbContext != null)
            {
                _logger.LogDebug("获取到已存在的上下文 {dbContextType} 实例，上下文标识：{dbContextHashCode}。", dbContext.GetType(), dbContext.GetHashCode());
                return dbContext;
            }

            dbContext = (DbContextBase)GetDbContextInternal(dbContextType);
            _logger.LogDebug("创建新的上下文 {dbContextType} 实例，上下文标识：{dbContextHashCode}。", dbContext.GetType(), dbContext.GetHashCode());

            return dbContext;
        }
        #endregion
        #region "获取指定类型的数据上下文实例集合"
        /// <summary>
        /// 获取指定类型的数据上下文实例集合
        /// </summary>
        /// <param name="dbContextTypes">上下文类型集合</param>
        /// <returns>数据上下文实例集合</returns>
        public IDbContext[] GetDbContexts(params Type[] dbContextTypes)
        {
            List<IDbContext> dbContexts = new();
            foreach (var dbContextType in dbContextTypes)
            {
                var dbContext = GetDbContext(dbContextType);
                if (dbContext != null)
                {
                    dbContexts.AddIfNotExist(dbContext);
                }
            }
            return dbContexts.ToArray();
        }
        #endregion
        #region "获取所有的数据上下文实例集合"
        /// <summary>
        /// 获取所有的数据上下文实例集合
        /// </summary>
        /// <returns>数据上下文实例集合</returns>
        public IDbContext[] GetAllDbContexts()
        {
            var dbContextTypes = AssemblyManager.FindTypesByBase(typeof(IDbContext));
            var dbContexts = GetDbContexts(dbContextTypes);
            return dbContexts;
        }
        #endregion
        #region "开启或应用所有数据上下文的事务"
        /// <summary>
        /// 开启或应用所有数据上下文的事务
        /// <para>对数据库连接开启事务或应用现有同连接对象的数据上下文事务</para>
        /// </summary>
        /// <param name="isolationLevel">事务隔离级别</param>
        public virtual void BeginOrUseTransaction(IsolationLevel? isolationLevel = null)
        {
            var dbContexts = GetAllDbContexts();
            if (dbContexts?.Length > 0)
            {
                foreach (var dbContext in dbContexts)
                {
                    BeginOrUseTransaction(dbContext, isolationLevel);
                }
            }
        }
        #endregion
        #region "开启或应用指定数据上下文的事务"
        /// <summary>
        /// 开启或应用指定数据上下文的事务
        /// <para>对数据库连接开启事务或应用现有同连接对象的数据上下文事务</para>
        /// </summary>
        /// <param name="context">数据上下文</param>
        /// <param name="isolationLevel">事务隔离级别</param>
        public virtual void BeginOrUseTransaction(IDbContext context, IsolationLevel? isolationLevel = null)
        {
            //事务隔离级别参数为null时，从配置中抓取一次。
            isolationLevel ??= context.GetOptions()?.IsolationLevel;

            if (_contextDict.IsEmpty || !IsEnabledTransaction)
            {
                return;
            }

            foreach (KeyValuePair<DbConnection, List<DbContextBase>> pair in _contextDict)
            {
                DbContextBase dbContext = pair.Value.FirstOrDefault(m => m.Equals((DbContextBase)context));
                if (dbContext == null)
                {
                    continue;
                }

                DbConnection connection = pair.Key;
                if (connection.State != ConnectionState.Open)
                {
                    connection.Open();
                }

                if (!_transDict.TryGetValue(connection, out DbTransaction transaction) || transaction.Connection == null)
                {
                    transaction = isolationLevel == null ? connection.BeginTransaction() : connection.BeginTransaction(isolationLevel.Value);
                    _transDict[connection] = transaction;
                    _logger.LogDebug("在上下文 {contextType}[{contextHashCode}] 创建事务，事务标识：{transactionHashCode}。", context.GetType(), context.GetHashCode(), transaction.GetHashCode());
                }

                if (dbContext.Database.CurrentTransaction != null && dbContext.Database.CurrentTransaction.GetDbTransaction() == transaction)
                {
                    continue;
                }

                if (dbContext.IsRelationalTransaction())
                {
                    dbContext.Database.UseTransaction(transaction);
                    _logger.LogDebug("在上下文 {contextType}[{contextHashCode}] 应用现有事务，事务标识：{transactionHashCode}。", context.GetType(), context.GetHashCode(), transaction.GetHashCode());
                }
                else
                {
                    if (isolationLevel == null)
                    {
                        dbContext.Database.BeginTransaction();
                    }
                    else
                    {
                        dbContext.Database.BeginTransaction(isolationLevel.Value);
                    }
                }
                break;
            }

            HasCommitted = false;
        }
        #endregion
        #region "启用事务，事务代码写在 UnitOfWork.EnableTransaction() 与 UnitOfWork.Commit() 之间"
        /// <summary>
        /// 启用事务
        /// <para>事务代码写在 UnitOfWork.EnableTransaction() 与 UnitOfWork.Commit() 之间</para>
        /// </summary>
        public void EnableTransaction()
        {
            string token = Guid.NewGuid().ToString();
            _transactionStack.Push(token);
            _logger.LogDebug("启用事务提交，标识：{token}，当前总标识数：{count}。", token, _transactionStack.Count);
        }
        #endregion
        #region "提交事务"
        /// <summary>
        /// 提交事务
        /// </summary>
        public virtual void Commit()
        {
            if (HasCommitted || _contextDict.IsEmpty || _transDict.IsEmpty)
            {
                return;
            }

            string token;
            if (_transactionStack.Count > 1)
            {
                token = _transactionStack.Pop();
                _logger.LogDebug("跳过事务提交，标识：{token}，当前剩余标识数：{count}。", token, _transactionStack.Count);
                return;
            }

            if (!IsEnabledTransaction)
            {
                throw new Exception(I18N.T("执行 IUnitOfWork.Commit() 之前，需要在事务开始时调用 IUnitOfWork.EnableTransaction()。"));
            }

            token = _transactionStack.Pop();
            foreach (KeyValuePair<DbConnection, DbTransaction> pair in _transDict)
            {
                DbConnection connection = pair.Key;
                DbTransaction transaction = pair.Value;
                transaction.Commit();
                _logger.LogDebug("提交事务，标识：{token}，事务标识：{transactionHashCode}。", token, transaction.GetHashCode());
                if (_contextDict.TryGetValue(connection, out List<DbContextBase> contexts))
                {
                    foreach (var context in contexts)
                    {
                        context.Database.UseTransaction(null);
                        _logger.LogDebug("上下文 {contextType}[{contextHashCode}] 释放事务，事务标识：{transactionHashCode}。", context.GetType(), context.GetHashCode(), transaction.GetHashCode());
                    }
                }
            }

            HasCommitted = true;
        }
        #endregion
        #region "回滚事务"
        /// <summary>
        /// 回滚事务
        /// </summary>
        public virtual void Rollback()
        {
            foreach (DbConnection connection in _transDict.Keys)
            {
                DbTransaction transaction = _transDict[connection];
                if (transaction.Connection == null)
                {
                    continue;
                }

                transaction.Rollback();
                _logger.LogDebug("回滚事务，事务标识：{transactionHashCode}。", transaction.GetHashCode());
            }

            HasCommitted = true;
        }
        #endregion

        #region "异步开启或应用所有数据上下文的事务"
        /// <summary>
        /// 异步开启或应用所有数据上下文的事务
        /// <para>异步对数据库连接开启事务或应用现有同连接对象的数据上下文事务</para>
        /// </summary>
        /// <param name="isolationLevel">事务隔离级别</param>
        /// <param name="cancellationToken">异步取消标记</param>
        /// <returns></returns>
        public virtual async Task BeginOrUseTransactionAsync(IsolationLevel? isolationLevel = null, CancellationToken cancellationToken = default)
        {
            var dbContexts = GetAllDbContexts();
            if (dbContexts?.Length > 0)
            {
                foreach (var dbContext in dbContexts)
                {
                    await BeginOrUseTransactionAsync(dbContext, isolationLevel, cancellationToken);
                }
            }
        }
        #endregion
        #region "异步开启或应用指定数据上下文的事务"
        /// <summary>
        /// 异步开启或应用指定数据上下文的事务
        /// <para>异步对数据库连接开启事务或应用现有同连接对象的数据上下文事务</para>
        /// </summary>
        /// <param name="context">数据上下文</param>
        /// <param name="isolationLevel">事务隔离级别</param>
        /// <param name="cancellationToken">异步取消标记</param>
        /// <returns></returns>
        public virtual async Task BeginOrUseTransactionAsync(IDbContext context, IsolationLevel? isolationLevel = null, CancellationToken cancellationToken = default)
        {
            //事务隔离级别参数为null时，从配置中抓取一次。
            isolationLevel ??= context.GetOptions()?.IsolationLevel;

            if (_contextDict.IsEmpty || !IsEnabledTransaction)
            {
                return;
            }

            foreach (KeyValuePair<DbConnection, List<DbContextBase>> pair in _contextDict)
            {
                DbContextBase dbContext = pair.Value.FirstOrDefault(m => m.Equals((DbContextBase)context));
                if (dbContext == null)
                {
                    continue;
                }

                DbConnection connection = pair.Key;
                if (connection.State != ConnectionState.Open)
                {
                    await connection.OpenAsync(cancellationToken);
                }

                if (!_transDict.TryGetValue(connection, out DbTransaction transaction) || transaction.Connection == null)
                {
                    transaction = isolationLevel == null ? await connection.BeginTransactionAsync(cancellationToken) : await connection.BeginTransactionAsync(isolationLevel.Value, cancellationToken);
                    _transDict[connection] = transaction;
                    _logger.LogDebug("在上下文 {contextType}[{contextHashCode}] 创建事务，事务标识：{transactionHashCode}。", context.GetType(), context.GetHashCode(), transaction.GetHashCode());
                }

                if (dbContext.Database.CurrentTransaction != null && dbContext.Database.CurrentTransaction.GetDbTransaction() == transaction)
                {
                    continue;
                }

                if (dbContext.IsRelationalTransaction())
                {
                    await dbContext.Database.UseTransactionAsync(transaction, cancellationToken: cancellationToken);
                    _logger.LogDebug("在上下文 {contextType}[{contextHashCode}] 上应用现有事务，事务标识：{transactionHashCode}。", context.GetType(), context.GetHashCode(), transaction.GetHashCode());
                }
                else
                {
                    if (isolationLevel == null)
                    {
                        await dbContext.Database.BeginTransactionAsync(cancellationToken);
                    }
                    else
                    {
                        await dbContext.Database.BeginTransactionAsync(isolationLevel.Value, cancellationToken);
                    }
                }
                break;
            }

            HasCommitted = false;
        }
        #endregion
        #region "异步提交事务"
        /// <summary>
        /// 异步提交事务
        /// </summary>
        /// <param name="cancellationToken">异步取消标记</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public virtual async Task CommitAsync(CancellationToken cancellationToken = default)
        {
            if (HasCommitted || _contextDict.IsEmpty || _transDict.IsEmpty)
            {
                return;
            }

            string token;
            if (_transactionStack.Count > 1)
            {
                token = _transactionStack.Pop();
                _logger.LogDebug("异步跳过事务提交，标识：{token}，当前剩余标识数：{count}。", token, _transactionStack.Count);
                return;
            }

            if (!IsEnabledTransaction)
            {
                throw new Exception(I18N.T("执行 IUnitOfWork.Commit() 之前，需要在事务开始时调用 IUnitOfWork.EnableTransaction()。"));
            }

            token = _transactionStack.Pop();
            foreach (var (connection, transaction) in _transDict)
            {
                await transaction.CommitAsync(cancellationToken);
                _logger.LogDebug("提交事务，标识：{token}，事务标识：{transactionHashCode}。", token, transaction.GetHashCode());
                if (_contextDict.TryGetValue(connection, out List<DbContextBase> contexts))
                {
                    foreach (var context in contexts)
                    {
                        await context.Database.UseTransactionAsync(null, cancellationToken);
                        _logger.LogDebug("上下文 {contextType}[{contextHashCode}] 释放事务，事务标识：{transactionHashCode}。", context.GetType(), context.GetHashCode(), transaction.GetHashCode());
                    }
                }
            }

            HasCommitted = true;
        }
        #endregion
        #region "异步回滚事务"
        /// <summary>
        /// 异步回滚事务
        /// </summary>
        /// <param name="cancellationToken">异步取消标记</param>
        /// <returns></returns>
        public virtual async Task RollbackAsync(CancellationToken cancellationToken = default)
        {
            foreach (DbConnection connection in _transDict.Keys)
            {
                DbTransaction transaction = _transDict[connection];
                if (transaction.Connection == null)
                {
                    continue;
                }

                await transaction.RollbackAsync(cancellationToken);
                _logger.LogDebug("异步回滚事务，事务标识：{transactionHashCode}。", transaction.GetHashCode());
            }

            HasCommitted = true;
        }
        #endregion

        #endregion

        #region "私有方法"
        #region "获取指定类型的上下文实例"
        /// <summary>
        /// 获取指定类型的上下文实例
        /// </summary>
        /// <param name="dbContextType">上下文类型</param>
        /// <returns>数据上下文实例</returns>
        private IDbContext GetDbContextInternal(Type dbContextType)
        {
            DbContextBase dbContext = (DbContextBase)_provider.GetRequiredService(dbContextType);
            //todo: IDatabaseHandler AllDbContext为true涉及多上下文,启动阶段会报错:先注释存储过程
            if (!dbContext.ExistsRelationalDatabase())
            {
                throw new Exception(I18N.T("数据上下文 {0} 的数据库不存在，请通过 Migration 功能进行数据迁移创建数据库。", dbContext.GetType().FullName));
            }

            //将连接对象DbConnection缓存到ScopedDictionary，在再次构建DbContextOptionsBuilder的时候可以直接使用
            DbContextOption dbContextOptions = _provider.GetMicroCloudOptions().GetDbContextOptions(dbContextType);
            ScopedDictionary scopedDictionary = _provider.GetRequiredService<ScopedDictionary>();
            DbConnection connection = dbContext.Database.GetDbConnection();
            scopedDictionary.TryAdd($"DbConnection_{dbContextOptions.ConnectionString}", connection);

            //缓存DbContext
            if (_contextDict.TryGetValue(connection, out List<DbContextBase> value))
            {
                value.Add(dbContext);
            }
            else
            {
                _contextDict.TryAdd(connection, new List<DbContextBase>() { dbContext });
            }

            return dbContext;
        }
        #endregion

        #endregion

        #region "释放资源"
        /// <summary>
        /// 释放资源
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            _contextDict.SelectMany(m => m.Value).ToList().ForEach(m => m.Dispose());
            _transDict.Values.ToList().ForEach(m => m.Dispose());

            base.Dispose(disposing);
        }
        #endregion

    }
    #endregion

}
